k8s 部署mihomo问题记录

🌿 Evergreen source k8s mihomo talos dns

k8s 部署 mihomo 问题记录

来源信息

  • 标题:Talos 单节点 K8s 部署 Mihomo,并接入已有 Blocky
  • 作者:
  • 链接:<local-project-path>
  • 时间:2026-05-09

一句话摘要

在已有 Talos 单节点 K8s 和 Blocky 的环境中,用 Mihomo Pod 替代 Clash VM / smartdns:Blocky 继续做主 DNS,代理域名转发到 Mihomo DNS,Mihomo 返回 fake-ip,再通过 RouterOS fake-ip 路由把流量送回 K8s 节点上的 Mihomo TUN。

项目当前结构

migraine_mihomo/
├── README.md
├── talos-k8s-mihomo-blocky-existing-guide.md
├── docs/
│   ├── pitfalls.md
│   └── troubleshooting-sop.md
├── k8s/mihomo/
│   ├── namespace.yaml
│   ├── secret.yaml
│   ├── configmap.yaml
│   ├── deployment.yaml
│   └── service.yaml
└── scripts/
    ├── custom-domains.txt
    └── update-blocky-conditional.sh

目标链路

客户端
  ↓ DNS
Blocky <main-dns-ip>:53
  ├─ 国内 / LAN / K8s 域名 → Blocky 原上游
  └─ 代理域名 → Mihomo DNS <mihomo-dns-ip>:1053
                         ↓
                      fake-ip 198.18.0.0/16
                         ↓
                  RouterOS 静态路由
                         ↓
              Talos 节点 <talos-node-ip>
                         ↓
                    Mihomo TUN
                         ↓
                      代理出口

核心原则:

  • Blocky 仍是客户端唯一主 DNS。
  • Mihomo 只处理代理域名 DNS、fake-ip、TUN 和代理转发。
  • RouterOS 只需要维护 198.18.0.0/16 -> <talos-node-ip> 的 fake-ip 路由。
  • Talos 宿主机不做手工改动,全部通过 K8s manifest 和 RouterOS 配置完成。

当前配置快照

项目 当前值 来源
K8s namespace network k8s/mihomo/namespace.yaml
Blocky DNS <main-dns-ip>:53 design / deployment DNS config
Mihomo LoadBalancer IP <mihomo-service-ip> k8s/mihomo/service.yaml
Talos Node IP <talos-node-ip> design / RouterOS 路由
NFS Server <nfs-server-ip> k8s/mihomo/deployment.yaml
NFS path <nfs-appdata-path> k8s/mihomo/deployment.yaml
Mihomo image metacubex/mihomo:v1.19.24 k8s/mihomo/deployment.yaml
Mihomo DNS <mihomo-dns-ip>:1053 k8s/mihomo/service.yaml
mixed-port <mihomo-service-ip>:7890 k8s/mihomo/service.yaml
fake-ip range 198.18.0.1/16 k8s/mihomo/configmap.yaml

部署顺序

项目 README 给出的顺序是:

kubectl apply -f namespace.yaml
kubectl apply -f secret.yaml
kubectl apply -f configmap.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

部署前确认:

  • MetalLB 地址池包含 <mihomo-service-ip>
  • NAS 上存在 <nfs-mihomo-path>
  • Talos 节点存在 /dev/net/tun
  • network namespace 允许 privileged Pod。
  • 不要把真实订阅 URL 和 controller secret 发布到公开文档。

关键 manifest 设计

Namespace

network namespace 加了 Pod Security privileged label:

pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/audit: privileged
pod-security.kubernetes.io/warn: privileged

Mihomo 需要 privileged: true/dev/net/tun,否则 TUN 无法创建。

Deployment

关键点:

  • hostNetwork: true:Mihomo 直接使用节点网络栈。
  • dnsPolicy: None,nameserver 指向 Blocky <main-dns-ip>
  • strategy: Recreate:避免 rollout 时新旧 Pod 在 hostNetwork 下抢同一组端口。
  • initContainer 和主容器使用同一个镜像 metacubex/mihomo:v1.19.24,避免 initContainer 额外拉镜像时被 TUN/DNS 问题卡死。
  • ConfigMap 只作为模板挂载到 /config-template,initContainer 复制到 NFS 可写目录 /data/config.yaml,主容器再把 NFS 子目录挂到 /root/.config/mihomo

ConfigMap

核心配置:

  • DNS 监听 0.0.0.0:1053
  • DNS 模式为 fake-ip
  • fake-ip-filter 排除 LAN、local、K8s 内部域名、NTP、代理节点域名和 registry 域名。
  • nameserver-policy 把 K8s 内部域名交给 Blocky,把订阅域名、代理节点域名、registry 域名交给国内 UDP DNS。
  • fallback 包含 223.5.5.5119.29.29.29,降低启动阶段 DoH 不可用带来的订阅拉取失败概率。
  • tun.route-exclude-address 排除 K8s Pod 网段、Service 网段、局域网网段,以及 Blocky 使用的上游 DNS IP。

当前排除网段:

route-exclude-address:
  - <pod-cidr>
  - <service-cidr>
  - <lan-cidr>
  - 223.5.5.5/32
  - 223.6.6.6/32
  - 114.114.114.114/32

Service

Service 使用 LoadBalancer,固定 IP 为 <mihomo-service-ip>,暴露:

  • DNS UDP/TCP:1053
  • mixed-port:7890
  • controller:当前 manifest 为 9090

Blocky 接入方式

Blocky 不整体替换,只更新 conditional mapping:

需要代理的域名 -> <mihomo-dns-ip>:1053
国内 / LAN / K8s 域名 -> Blocky 原有上游

项目里提供了 scripts/update-blocky-conditional.sh

  • 从 GFW 域名源拉取域名。
  • 合并 scripts/custom-domains.txt
  • 更新 Blocky Helm values 的 conditional.mapping
  • 默认 Mihomo DNS 为 <mihomo-dns-ip>:1053

custom-domains.txt 里维护 GFW 列表未覆盖、但需要走 Mihomo 的域名,例如 claude.aianthropic.com、容器镜像仓库和 Talos factory 相关域名。

RouterOS 配置

fake-ip 静态路由:

/ip route add dst-address=198.18.0.0/16 gateway=<talos-node-ip> comment="mihomo fake-ip via talos k8s" distance=1

客户端 DHCP DNS 应继续指向 Blocky:

DNS = <main-dns-ip>

不要把 DHCP DNS 改成 <mihomo-dns-ip>:1053,客户端 DNS 配置没有端口概念,而且 Mihomo 不应成为主 DNS。

Mihomo 重启后,如果 RouterOS 缓存了旧 fake-ip 映射,需要清缓存:

/ip dns cache flush

建议降低 RouterOS DNS 缓存 TTL:

/ip dns set cache-max-ttl=1m

验证清单

Pod 和 Service

kubectl get pods -n network -o wide
kubectl get svc -n network mihomo
kubectl logs -n network deploy/mihomo

预期:

  • Pod 为 Running
  • Service EXTERNAL-IP<mihomo-service-ip>
  • 日志中没有 parse config errorbind: address already in usetun create failed

DNS 链路

dig google.com @<mihomo-dns-ip> -p 1053
dig google.com @<main-dns-ip>
dig baidu.com @<main-dns-ip>
dig kubernetes.default.svc.cluster.local @<main-dns-ip>

预期:

查询 预期
google.com @<mihomo-dns-ip> -p 1053 返回 198.18.x.x
google.com @<main-dns-ip> 返回 198.18.x.x
baidu.com @<main-dns-ip> 返回真实国内 IP
*.cluster.local @<main-dns-ip> 返回真实 K8s 内部 IP

TUN 和出口

kubectl exec -n network deploy/mihomo -- ip link
curl https://ipinfo.io

预期:

  • Pod 内能看到 Mihomo 创建的 TUN 相关接口。
  • 访问代理域名时,出口 IP 为代理节点。

踩坑记录

1. ConfigMap 只读导致启动失败

现象:Pod 启动后崩溃,日志有写入权限错误。

原因:ConfigMap 直接挂到 /root/.config/mihomo 后目录只读,而 Mihomo 启动时要写 cache.dbproviders/ 等文件。

解法:ConfigMap 只做模板;initContainer 复制配置到 NFS 可写目录,主容器把整个工作目录挂到 NFS。

2. initContainer 镜像拉取被 TUN/DNS 问题卡死

现象:initContainer ImagePullBackOff,日志显示 TLS 或 DNS 连接失败。

原因:initContainer 使用未缓存镜像时需要拉取,拉取流量又可能被未完全正常的 TUN/DNS 链路影响。

解法:initContainer 使用和主容器相同的 metacubex/mihomo:v1.19.24,复用本地已缓存镜像。

3. hostNetwork rollout 端口冲突

现象:kubectl rollout restart 后新 Pod CrashLoopBackOff,日志报 bind: address already in use

原因:hostNetwork: true 下新旧 Pod 共用节点网络栈,默认 RollingUpdate 会先启新 Pod。

解法:Deployment 使用 strategy: Recreate

4. K8s 内部流量被 TUN 劫持

现象:*.svc.cluster.local 被解析成 fake-ip,集群内部访问异常。

原因:auto-route: true 会把节点上流量导入 TUN,未排除 Pod/Service 网段时会误伤集群内部流量。

解法:

  • tun.route-exclude-address 排除 <pod-cidr><service-cidr>
  • nameserver-policy*.cluster.local*.svc.cluster.local 指向 Blocky。

5. Blocky 上游 DNS 查询被 TUN 劫持

现象:baidu.com 查询 Blocky 返回 198.18.x.x,国内域名被 fake-ip 化。

原因:Blocky 的上游 DNS 请求也在节点网络里,可能被 Mihomo TUN 接管。

解法:route-exclude-address 加入 Blocky 的上游 DNS IP,例如 223.5.5.5/32223.6.6.6/32114.114.114.114/32,让 Blocky 上游查询绕开 TUN。<lan-cidr> 可保留为局域网流量保护,但不是这个问题的直接修复点。

6. 代理节点域名被 fake-ip 劫持回环

现象:日志出现 dial Proxy ... dns resolve failed,代理连接失败。

原因:代理节点域名被 Mihomo DNS 解析为 fake-ip,流量重新进入 TUN,形成回环。

解法:代理节点域名要同时加入:

  • fake-ip-filter
  • nameserver-policy
  • 必要时加入 rulesDIRECT

7. 订阅域名启动时解析失败

现象:启动日志出现 initial proxy provider subscription error

原因:启动阶段 TUN、DoH、代理组还未完全可用,订阅域名解析或连接失败。

解法:

  • 订阅域名写进 nameserver-policy,指向 223.5.5.5
  • fallback 加国内 UDP DNS。
  • proxy-providers.subscription.proxy 设置为 DIRECT

8. RouterOS DNS 缓存旧 fake-ip

现象:Mihomo 重启后,客户端仍拿到旧 fake-ip 映射,访问失败。

原因:RouterOS 作为客户端默认 DNS 的中间缓存层,保留了 Mihomo 重启前的 fake-ip 映射。

解法:Mihomo 重启后执行 /ip dns cache flush,并把 RouterOS cache-max-ttl 调低。

新增异常域名处理规则

每次发现新域名被误劫持或解析异常,优先按这个顺序处理:

  1. dig <domain> @<mihomo-dns-ip> -p 1053,确认 Mihomo DNS 返回的是 fake-ip 还是真实 IP。
  2. dig <domain> @<main-dns-ip>,确认 Blocky 是否把域名转发给 Mihomo。
  3. 如果这是代理节点、订阅、registry、工具域名,不应 fake-ip 回环,加入 fake-ip-filter
  4. 需要启动阶段就可解析的域名,加入 nameserver-policy,通常指向 223.5.5.5
  5. 需要明确直连的域名,加入 rulesDIRECT
  6. 需要走代理的普通域名,加入 scripts/custom-domains.txt,再运行 Blocky conditional 更新脚本。

我的理解

这套方案的关键不是“让所有 DNS 都进 Mihomo”,而是把职责拆清楚:

  • Blocky 负责主 DNS、国内/LAN/K8s 解析和域名分流。
  • Mihomo 负责需要代理的域名解析成 fake-ip,并用 TUN 接住 fake-ip 流量。
  • RouterOS 负责把 198.18.0.0/16 送回 Talos 节点。
  • NFS 负责给 Mihomo 工作目录提供可写持久化,绕开 ConfigMap 只读问题。

最容易出问题的地方是启动时序和流量回环:Mihomo 自己拉订阅、解析代理节点、K8s 内部访问、Blocky 上游查询,都可能被 TUN 误劫持。所以新增域名时不要只改一个地方,要同时考虑 fake-ip、nameserver-policy、rules 和 Blocky conditional mapping。